# -*- coding: utf-8 -*-

"""
一个练习项目，用Python操作手机上的2048游戏
同时，结合一定的算法，看看能够达到的最大分

copyright: chysun2000@hotmail.com
"""

import math
from adb import AdbTool
from constant import SWIPE_LEFT, SWIPE_RIGHT, SWIPE_UP, SWIPE_DOWN, SWIPE_ERROR


class StepOperation:
    def __init__(self, step, opt):
        self.step = step
        self.operation = opt

    def __str__(self):
        return 'STEP:' + str(self.step) + 'MOVE:' + AdbTool.get_swipe_string(self.operation)


class GameStatus:
    GAME_GOING = 0
    GAME_OVER = 1
    SCREEN_CAP_TEMP_FILE_NAME = 'screen_cap_step-'

    def __init__(self):
        self.step = 0
        self.score = 0
        self.status = self.GAME_GOING
        self.step_operations = []
        self.last_operation = SWIPE_ERROR

    def is_game_over(self):
        return self.status == self.GAME_OVER

    def inc_step(self):
        self.step += 1

    def get_screen_file_name(self):
        value = self.step % 32
        return self.SCREEN_CAP_TEMP_FILE_NAME + str(value) + '.png'

    def record_step_operation(self, operation):
        self.last_operation = operation
        step_opt = StepOperation(self.step, operation)
        self.step_operations.append(step_opt)
        if operation is SWIPE_ERROR:
            self.status = GameStatus.GAME_OVER
        else:
            self.status = GameStatus.GAME_GOING

    def get_last_operation(self):
        return self.last_operation

    def get_step(self):
        return self.step

    def __str__(self):
        return '得分：' + str(self.score) + '，步数：' + str(self.step)


class Square:
    SQUARE_2 = 2
    SQUARE_4 = 4
    SQUARE_EMPTY = 0

    def __init__(self, row, col):
        self.__value = Square.SQUARE_EMPTY
        self.row = row
        self.col = col

    def set_value(self, value):
        self.__value = value

    def get_value(self):
        return self.__value

    def is_empty(self):
        return self.__value == Square.SQUARE_EMPTY

    def __str__(self):
        return "[" + str(self.row) + "]" + "[" + str(self.col) + "]" + '=' + str(self.__value)


class GameBoard:
    ROW_COUNT = 4
    COL_COUNT = 4
    INVALID_VALUE = -1

    def __init__(self):
        self.matrix = [[Square(i, j) for i in range(4)] for j in range(4)]
        for i in range(GameBoard.ROW_COUNT):
            for j in range(GameBoard.COL_COUNT):
                self.matrix[i][j] = Square(i, j)

    def load_matrix(self, squares):
        for i in range(GameBoard.ROW_COUNT):
            for j in range(GameBoard.COL_COUNT):
                self.matrix[i][j] = squares[i * GameBoard.COL_COUNT + j]

    def equals(self, board):
        for i in range(GameBoard.ROW_COUNT):
            for j in range(GameBoard.COL_COUNT):
                if int(board.get_value(i, j)) != int(self.get_value(i, j)):
                    return False
        return True

    def get_empty_squares(self):
        empty_squares = []
        for i in range(GameBoard.ROW_COUNT):
            for j in range(GameBoard.COL_COUNT):
                square = self.matrix[i][j]
                if square.is_empty():
                    empty_squares.append(square)
        return empty_squares

    def clear(self):
        for i in range(GameBoard.ROW_COUNT):
            for j in range(GameBoard.COL_COUNT):
                self.set_value(i, j, Square.SQUARE_EMPTY)

    def set_square_value(self, square, value):
        self.set_value(square.row, square.col, value)

    def set_value(self, row, col, value):
        if row >= GameBoard.ROW_COUNT or col >= GameBoard.COL_COUNT:
            pass
        else:
            square = self.matrix[row][col]
            square.set_value(value)

    def get_value(self, row, col):
        square = self.matrix[row][col]
        if square:
            return square.get_value()
        else:
            print('[ERROR]Square is not inited!!!')
            return 0

    def copy_new(self):
        new_board = GameBoard()
        for i in range(GameBoard.ROW_COUNT):
            for j in range(GameBoard.COL_COUNT):
                value = int(self.get_value(i, j))
                new_board.set_value(i, j, value)

        return new_board

    def merge_cell(self, values, start_index):
        values_length = len(values)
        if start_index < values_length and start_index + 1 < values_length:
            value = values[start_index]
            if value == values[start_index + 1]:
                values[start_index] = 2 * value
                values[start_index + 1] = 0
                start_index = start_index + 2
            else:
                start_index = start_index + 1

            self.merge_cell(values, start_index)

    def move_left(self):
        for i in range(GameBoard.ROW_COUNT):
            # remove 0
            not_zero_values = [0, 0, 0, 0]
            index = 0
            for j in range(GameBoard.COL_COUNT):
                value = self.get_value(i, j)
                self.set_value(i, j, Square.SQUARE_EMPTY)
                if value != Square.SQUARE_EMPTY:
                    not_zero_values[index] = int(value)
                    index += 1

            self.merge_cell(not_zero_values, 0)

            col_index = 0
            for index in range(len(not_zero_values)):
                if not_zero_values[index] != 0:
                    self.set_value(i, col_index, int(not_zero_values[index]))
                    col_index += 1

    def move_right(self):
        for i in range(GameBoard.ROW_COUNT):
            # remove 0
            not_zero_values = [0, 0, 0, 0]
            index = 0
            for j in reversed(range(GameBoard.COL_COUNT)):
                value = self.get_value(i, j)
                self.set_value(i, j, Square.SQUARE_EMPTY)
                if value != 0:
                    not_zero_values[index] = value
                    index += 1

            self.merge_cell(not_zero_values, 0)

            not_zero_values.reverse()
            col_index = 3
            for index in reversed(range(GameBoard.COL_COUNT)):
                if not_zero_values[index] != 0:
                    self.set_value(i, col_index, int(not_zero_values[index]))
                    col_index -= 1

    def move_up(self):
        for j in range(GameBoard.COL_COUNT):
            not_zero_values = [0, 0, 0, 0]
            index = 0
            # remove 0
            for i in range(GameBoard.ROW_COUNT):
                value = self.get_value(i, j)
                self.set_value(i, j, Square.SQUARE_EMPTY)
                if value != Square.SQUARE_EMPTY:
                    not_zero_values[index] = int(value)
                    index += 1

            self.merge_cell(not_zero_values, 0)

            row_index = 0
            for index in range(GameBoard.ROW_COUNT):
                if not_zero_values[index] != 0:
                    self.set_value(row_index, j, int(not_zero_values[index]))
                    row_index += 1

    def move_down(self):
        for j in range(GameBoard.COL_COUNT):
            # remove 0
            not_zero_values = [0, 0, 0, 0]
            index = 0
            for i in reversed(range(GameBoard.ROW_COUNT)):
                value = self.get_value(i, j)
                self.set_value(i, j, Square.SQUARE_EMPTY)
                if value != Square.SQUARE_EMPTY:
                    not_zero_values[index] = value
                    index += 1

            self.merge_cell(not_zero_values, 0)

            not_zero_values.reverse()
            row_index = 3
            for index in reversed(range(GameBoard.ROW_COUNT)):
                if not_zero_values[index] != 0:
                    self.set_value(row_index, j, int(not_zero_values[index]))
                    row_index -= 1

    def move(self, operation):
        # print('-----------Before moving -----------')
        # print(self)
        # print('Move ' + AdbTool.get_swipe_string(operation) + '------------------')
        if operation == SWIPE_LEFT:
            self.move_left()
        elif operation == SWIPE_RIGHT:
            self.move_right()
        elif operation == SWIPE_UP:
            self.move_up()
        elif operation == SWIPE_DOWN:
            self.move_down()
        else:
            print('[ERROR]unsupported operation!!!' + str(operation))
        # print(self)
        # print('-----------Moving End---------------')

    def empty_cells_in_row(self, row):
        empty_count = 0
        for j in range(GameBoard.COL_COUNT):
            square = self.matrix[row][j]
            if square.is_empty():
                empty_count += 1

        return math.pow(2, empty_count)

    def empty_cells_in_col(self, col):
        empty_count = 0
        for i in range(GameBoard.ROW_COUNT):
            square = self.matrix[i][col]
            if square.is_empty():
                empty_count += 1

        return math.pow(2, empty_count)

    def total_empty_cells(self):
        empty_count = 0
        for i in range(GameBoard.ROW_COUNT):
            for j in range(GameBoard.COL_COUNT):
                square = self.matrix[i][j]
                if square.is_empty():
                    empty_count += 1

        return empty_count

    def get_merge_count_in_array(self, value_array):
        merge_count = 0
        empty_count = 0

        for value in value_array:
            if int(value) == Square.SQUARE_EMPTY:
                empty_count += 1

        self.merge_cell(value_array, 0)
        for value in value_array:
            if int(value) == Square.SQUARE_EMPTY:
                merge_count += 1

        if empty_count != 4:
            merge_count -= empty_count
        else:
            merge_count = 0

        if merge_count < 0:
            merge_count = 0

        return merge_count

    def get_row_array(self, row):
        values = []
        for j in range(GameBoard.COL_COUNT):
            values.append(int(self.get_value(row, j)))
        return values

    def merges_in_row(self, row):
        row_values = self.get_row_array(row)
        merge_count = self.get_merge_count_in_array(row_values)

        row_values.reverse()
        reverse_count = self.get_merge_count_in_array(row_values)

        merge_count = max(merge_count, reverse_count)
        if merge_count == 0:
            return 0
        else:
            return math.pow(2, merge_count)

    def get_col_array(self, col):
        values = []
        for i in range(GameBoard.ROW_COUNT):
            values.append(int(self.get_value(i, col)))
        return values

    def merges_in_col(self, col):
        col_values = self.get_col_array(col)

        merge_count = self.get_merge_count_in_array(col_values)
        col_values.reverse()
        merge_count += self.get_merge_count_in_array(col_values)

        if merge_count == 0:
            return 0
        else:
            return math.pow(2, merge_count)

    def max_value_in_row(self, row):
        max_value = 0
        for j in range(GameBoard.COL_COUNT):
            value = int(self.get_value(row, j))
            if value > max_value:
                max_value = value

        return max_value

    def max_value_in_col(self, col):
        max_value = 0
        for i in range(GameBoard.ROW_COUNT):
            value = int(self.get_value(i, col))
            if value > max_value:
                max_value = value

        return max_value

    @staticmethod
    def max_value_in_array(values):
        max_value = 0
        for value in values:
            if value > max_value:
                max_value = value
        return max_value

    def monotonicity_in_row(self, row):
        values = self.get_row_array(row)
        org_values = values.copy()
        values.sort()
        if values == org_values:
            return 1 * GameBoard.array_sum(values)
        else:
            return -1 * GameBoard.array_sum(values)

    def monotonicity_in_col(self, col):
        values = self.get_col_array(col)
        org_values = values.copy()
        values.sort()
        if values == org_values:
            return 1 * GameBoard.array_sum(values)
        else:
            return -1 * GameBoard.array_sum(values)

    def get_max_cell(self):
        max_value = 0
        for i in range(GameBoard.ROW_COUNT):
            for j in range(GameBoard.COL_COUNT):
                value = int(self.get_value(i, j))
                if value > max_value:
                    max_value = value
        return max_value

    def is_reg_ok(self):
        for i in range(GameBoard.ROW_COUNT):
            for j in range(GameBoard.COL_COUNT):
                value = str(self.get_value(i, j))
                if not value.isnumeric():
                    return False
        return True

    @staticmethod
    def array_sum(values):
        value = 0
        for v in values:
            value += int(v)
        return value

    def __str__(self):
        info = ''
        for i in range(GameBoard.ROW_COUNT):
            for j in range(GameBoard.COL_COUNT):
                square = self.matrix[i][j]
                info = info + square.__str__()
                if j < GameBoard.COL_COUNT - 1:
                    info = info + ", "
            if i < GameBoard.ROW_COUNT - 1:
                info = info + "\n"
        return info
